package aynu.oracle.video.service.Impl;

import aynu.oracle.video.pojo.FileInfo;
import aynu.oracle.video.util.VideoUtil;
import aynu.oracle.video.util.fileLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.locks.Lock;

/**
 * 文件上传的逻辑方法
 * 包括分片的验证，分片的上传，分片的合并及分片的删除
 */
@Service
@Scope("prototype")
public class webUploader {

    private final static Logger log = LoggerFactory.getLogger(webUploader.class);

    /**
     * 错误详情
     */
    private String msg;

    @Value("D:\\文件\\Ideaworkspace\\video\\src\\main\\resources\\static\\file\\")
    private String coverPath;

    /**
     * 分片验证
     * 验证对应分片文件是否存在，大小是否吻合
     * @param file  分片文件的路径
     * @param size  分片文件的大小
     * @return boolean
     */
    public boolean chunkCheck(String file, Long size){
        //检查目标分片是否存在且完整
        File target = new File(file);
        if(target.isFile() && size == target.length()){
            return true;
        }else{
            return false;
        }
    }

    /**
     * 分片合并操作
     * 要点:
     *  > 合并: NIO
     *  > 并发锁: 避免多线程同时触发合并操作
     *  > 清理: 合并清理不再需要的分片文件、文件夹、tmp文件
     * @param folder    分片文件所在的文件夹名称
     * @param ext       合并后的文件后缀名
     * @param chunks    分片总数
     * @param md5       文件签名
     * @param path      合并后的文件所存储的位置
     * @return string
     */
    public String chunksMerge(String folder, String ext, int chunks, String md5, String path){
//        System.out.println("分片合并操作："+folder+"_"+ext+"_"+chunks+"_"+md5+"_"+path);
        //合并后的目标文件
        String target;
        String videoTime = null;
        //检查是否满足合并条件：分片数量是否足够
//        System.out.println("chunks数量——"+chunks+"___实际数量——"+this.getChunksNum(path + "/" + folder));
        if(chunks == this.getChunksNum(path + "/" + folder) && this.getChunks(path + "/" +folder) != null){
            // 同步指定合并的对象，即加锁
            Lock lock = fileLock.getLock(folder);
            lock.lock();
            webUploader uploader = new webUploader();
            // 用来合并分片
            FileChannel outChannel = null;
            try{
                //检查是否满足合并条件：分片数量是否足够
//                System.out.println("路径__"+path + "/" +folder);
//                System.out.println("分片对象————"+this.getChunks(path + "/" +folder));
                // 加了一层 分片的循环判断
                if(this.getChunks(path + "/" + folder) != null){
                    List<File> files = new ArrayList<File>(Arrays.asList(this.getChunks(path + "/" +folder)));
                    if(chunks == files.size()){
                        //按照名称排序文件，这里分片都是按照数字命名的
                        Collections.sort(files, new Comparator<File>() {
                            @Override
                            public int compare(File o1, File o2) {
                                if(Integer.valueOf(o1.getName()) < Integer.valueOf(o2.getName())){
                                    return -1;
                                }
                                return 1;
                            }
                        });
                        //创建合并后的文件
                        File outputFile = new File(path + "/" + this.randomFileName(ext));
                        if(outputFile.exists()){
                            log.error("文件[" + folder + "]随机命名冲突");
                            this.setErrorMsg("文件随机命名冲突");
                            return null;
                        }
                        outputFile.createNewFile();
                        outChannel = new FileOutputStream(outputFile).getChannel();
                        //合并
                        FileChannel inChannel;
                        for(File file : files){
                            inChannel = new FileInputStream(file).getChannel();
                            inChannel.transferTo(0, inChannel.size(), outChannel);
                            inChannel.close();
                            //删除分片
                            if(!file.delete()){
                                log.error("分片[" + folder + "=>" + file.getName() + "]删除失败");
                            }
                        }
                        outChannel.close();
    //                    files = null;
                        //清理：文件夹，tmp文件
                        boolean flag = this.cleanSpace(folder, path);
    //                    System.out.println("Flag ----"+flag);
                        String canonicalPath = outputFile.getCanonicalPath();
                        // 获取视频时长
                        videoTime = VideoUtil.getVideoTime(outputFile);
                        String pic_name = outputFile.getName();
                        String pic_path = pic_name.substring(0, pic_name.lastIndexOf('.'));
    //                    System.out.println("获取视频截图 --->  "+pic_path);
                        VideoUtil.getVideoPic(outputFile, coverPath+pic_path+".jpg");
                        return  outputFile.getName()+" "+videoTime;
                    }
                }
            }catch(Exception ex){
                if(outChannel!=null){
                    try {
                        outChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                this.cleanSpace(folder, path);
                log.error("数据分片合并失败", ex);
                this.setErrorMsg("数据分片合并失败");
                return null;
            }finally {
                //解锁
                lock.unlock();
                //清理锁对象
                fileLock.removeLock(folder);
            }
        }else{
            // 清除存放分片的文件夹，tmp文件
            this.cleanSpace(folder, path);
        }
        return null;  // 分片数目不够
    }

    /**
     * 为上传的文件创建对应的保存位置
     * 若上传的是分片，则会创建对应的文件夹结构和tmp文件
     * @param info  上传文件的相关信息
     * @param path  文件保存根路径
     * @return 被上传的文件对象
     */
    public File getReadySpace(FileInfo info, String path){
        //创建上传文件所需的文件夹
        if(!this.createFileFolder(path, false)){
            return null;
        }
        String newFileName;	//上传文件的新名称
        //如果是分片上传，则需要为分片创建文件夹
        if (info.getChunks() > 0) {
            newFileName = String.valueOf(info.getChunk());
            String fileFolder = this.md5(info.getUserId() + info.getName() + info.getType() + info.getLastModifiedDate() + info.getSize());
            if(fileFolder == null){
                return null;
            }
            path += "/" + fileFolder;    //文件上传路径更新为指定文件信息签名后的临时文件夹，用于后期合并
            if(!this.createFileFolder(path, true)){
                return null;
            }
        } else {
            //生成随机文件名
            newFileName = this.randomFileName(info.getName());
        }
        return new File(path, newFileName);
    }

    /**
     * 清理分片上传的相关数据
     * 文件夹，tmp文件
     * @param folder    文件夹名称
     * @param path      上传文件根路径
     * @return boolean 是否成功清理分片
     */
    private boolean cleanSpace(String folder, String path){
        // 获取该文件夹下的分片
//        System.out.println("folder___"+folder);
        File[] chunks = this.getChunks(path + "/" + folder);
//        if(chunks !=null) System.out.println("length--"+chunks.length);
        if(folder!=null && chunks != null && chunks.length >= 0){
            List<File> files = new ArrayList<File>(Arrays.asList(this.getChunks(path + "/" +folder)));
//            System.out.println("files_list_size="+files.size());
            // 循环删除分片
            for(File file : files){
                //删除分片
                file.delete();
            }
            // 删除存放分片文件夹
            File garbage = new File(path + "\\"+folder);
//            System.out.println("url___"+path + "/" + folder);
            if(garbage.exists()){
                garbage.delete();
            }
            //删除tmp文件
            garbage = new File(path + "/" + folder + ".tmp");
            if(garbage.exists()){
                garbage.delete();
            }
        }
        return true;
    }

    /**
     * 获取指定文件的所有分片
     * @param folder    文件夹路径
     * @return 分片的文件对象数组
     */
    private File[] getChunks(String folder){
        File targetFolder = new File(folder);
        return targetFolder.listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                if (file.isDirectory()) {
                    return false;
                }
                return true;
            }
        });
    }

    /**
     * 获取指定文件的分片数量
     * @param folder    文件夹路径
     * @return int 分片数目
     */
    private int getChunksNum(String folder){
       File[] filesList = this.getChunks(folder);
       if(filesList==null) return 0;
       return filesList.length;
    }

    /**
     * 创建存放上传的文件的文件夹
     * @param file  文件夹路径
     * @return 是否成功创建文件夹
     */
    private boolean createFileFolder(String file, boolean hasTmp){
        //创建存放分片文件的临时文件夹
        File tmpFile = new File(file);
        if(!tmpFile.exists()){
            try {
                tmpFile.mkdir();
            }catch(SecurityException ex){
                log.error("无法创建文件夹", ex);
                this.setErrorMsg("无法创建文件夹");
                return false;
            }
        }
        if(hasTmp){
            //创建一个对应的文件，用来记录上传分片文件的修改时间，用于清理长期未完成的垃圾分片
            tmpFile = new File(file + ".tmp");
            if(tmpFile.exists()){
                tmpFile.setLastModified(System.currentTimeMillis());
            }else{
                try{
                    tmpFile.createNewFile();
                }catch(IOException ex){
                    log.error("无法创建tmp文件", ex);
                    this.setErrorMsg("无法创建tmp文件");
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * 为上传的文件生成随机名称
     * @param originalName  文件的原始名称，主要用来获取文件的后缀名
     * @return string 随机名称
     */
    private String randomFileName(String originalName){
        String ext[] = originalName.split("\\.");
        return UUID.randomUUID().toString() + "." + ext[ext.length-1];
    }

    /**
     * MD5签名
     * @param content   要签名的内容
     * @return string md5标签，用于秒传和断点续传
     */
    private String md5(String content){
        StringBuffer sb = new StringBuffer();
        try{
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.update(content.getBytes("UTF-8"));
            byte[] tmpFolder = md5.digest();

            for(int i = 0; i < tmpFolder.length; i++){
                sb.append(Integer.toString((tmpFolder[i] & 0xff) + 0x100, 16).substring(1));
            }
            return sb.toString();
        }catch(NoSuchAlgorithmException ex){
            log.error("无法生成文件的MD5签名", ex);
            this.setErrorMsg("无法生成文件的MD5签名");
            return null;
        }catch(UnsupportedEncodingException ex){
            log.error("无法生成文件的MD5签名", ex);
            this.setErrorMsg("无法生成文件的MD5签名");
            return null;
        }
    }

    /**
     * 记录异常错误信息
     * @param msg   错误详细
     */
    private void setErrorMsg(String msg){
        this.msg = msg;
    }

    /**
     * 获取错误详细
     * @return string
     */
    public String getErrorMsg(){
        return this.msg;
    }

    /**
     * 删除视频文件
     * @param folder_path 视频文件的路径
     * @return boolean
     */
    public boolean deleteVideoFile(String folder_path){
        File file = new File(folder_path);
        if(file.exists()){
            return file.delete();
        }
        return false;
    }
}
